--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 1c2977fe00387d0e421e3f871f9fcd9b200fad1c
Parents : 68d433b
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-16T12:04:45-05:00
feat: implement Android privacy features to block screenshots and clear clipboard on background
Changes
17 files changed, 349 insertions(+), 3 deletions(-)
Diff
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 82e87548..0a694773 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -34,7 +34,7 @@
<application
android:name="com.meshchatx.MeshChatApplication"
- android:allowBackup="true"
+ android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
diff --git a/android/app/src/main/java/com/meshchatx/MainActivity.java b/android/app/src/main/java/com/meshchatx/MainActivity.java
index 2f7015fa..127e99d6 100644
--- a/android/app/src/main/java/com/meshchatx/MainActivity.java
+++ b/android/app/src/main/java/com/meshchatx/MainActivity.java
@@ -4,6 +4,7 @@ import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -23,6 +24,7 @@ import android.os.PowerManager;
import android.content.IntentFilter;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.view.WindowManager;
import android.webkit.CookieManager;
import android.webkit.GeolocationPermissions;
import android.webkit.PermissionRequest;
@@ -71,6 +73,8 @@ import org.json.JSONObject;
public class MainActivity extends AppCompatActivity {
private static final String SHELL_PREFS = "meshchatx_shell";
private static final String PREF_UI_THEME = "ui_theme";
+ private static final String PREF_BLOCK_SCREENSHOTS = "block_screenshots";
+ private static final String PREF_CLEAR_CLIPBOARD_ON_BACKGROUND = "clear_clipboard_on_background";
private static final String THEME_DARK = "dark";
private static final String THEME_LIGHT = "light";
@@ -190,6 +194,9 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
setContentView(R.layout.activity_main);
+ // Mitigate overlay tapjacking without changing user-visible behavior.
+ getWindow().getDecorView().setFilterTouchesWhenObscured(true);
+ applyBlockScreenshots(isBlockScreenshotsEnabled());
webView = findViewById(R.id.webView);
progressBar = findViewById(R.id.progressBar);
@@ -533,6 +540,9 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onStop() {
+ if (isClearClipboardOnBackgroundEnabled()) {
+ clearPrimaryClipboard();
+ }
if (!isFinishing() && !backendFailed) {
ContextCompat.startForegroundService(this, new Intent(this, MeshChatForegroundService.class));
}
@@ -894,6 +904,56 @@ public class MainActivity extends AppCompatActivity {
}
}
+ private boolean isBlockScreenshotsEnabled() {
+ return getSharedPreferences(SHELL_PREFS, MODE_PRIVATE)
+ .getBoolean(PREF_BLOCK_SCREENSHOTS, false);
+ }
+
+ private void setBlockScreenshotsEnabled(boolean enabled) {
+ getSharedPreferences(SHELL_PREFS, MODE_PRIVATE)
+ .edit()
+ .putBoolean(PREF_BLOCK_SCREENSHOTS, enabled)
+ .apply();
+ applyBlockScreenshots(enabled);
+ }
+
+ private void applyBlockScreenshots(boolean enabled) {
+ if (enabled) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
+ }
+ }
+
+ private boolean isClearClipboardOnBackgroundEnabled() {
+ return getSharedPreferences(SHELL_PREFS, MODE_PRIVATE)
+ .getBoolean(PREF_CLEAR_CLIPBOARD_ON_BACKGROUND, false);
+ }
+
+ private void setClearClipboardOnBackgroundEnabled(boolean enabled) {
+ getSharedPreferences(SHELL_PREFS, MODE_PRIVATE)
+ .edit()
+ .putBoolean(PREF_CLEAR_CLIPBOARD_ON_BACKGROUND, enabled)
+ .apply();
+ }
+
+ private void clearPrimaryClipboard() {
+ try {
+ ClipboardManager clipboard =
+ (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipboard == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ clipboard.clearPrimaryClip();
+ } else {
+ clipboard.setPrimaryClip(ClipData.newPlainText("", ""));
+ }
+ } catch (Exception ignored) {
+ // Clipboard access can fail on locked devices or OEM builds.
+ }
+ }
+
private String toStackTrace(Throwable error) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
@@ -1270,6 +1330,26 @@ public class MainActivity extends AppCompatActivity {
activity.runOnUiThread(() -> activity.setUiThemeFromBridge(theme));
}
+ @JavascriptInterface
+ public boolean getBlockScreenshots() {
+ return activity.isBlockScreenshotsEnabled();
+ }
+
+ @JavascriptInterface
+ public void setBlockScreenshots(boolean enabled) {
+ activity.runOnUiThread(() -> activity.setBlockScreenshotsEnabled(enabled));
+ }
+
+ @JavascriptInterface
+ public boolean getClearClipboardOnBackground() {
+ return activity.isClearClipboardOnBackgroundEnabled();
+ }
+
+ @JavascriptInterface
+ public void setClearClipboardOnBackground(boolean enabled) {
+ activity.runOnUiThread(() -> activity.setClearClipboardOnBackgroundEnabled(enabled));
+ }
+
@JavascriptInterface
public String getSidebandPluginsDefaultPath() {
try {
diff --git a/meshchatx.rsm b/meshchatx.rsm
index 49a354bf..52341376 100644
Binary files a/meshchatx.rsm and b/meshchatx.rsm differ
diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index 4182d4ee..a95c1a1e 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -919,11 +919,43 @@
<header class="settings-section__header">
<div>
<div class="settings-section__eyebrow">Android</div>
- <h2>{{ $t("settings.share_apk_heading") }}</h2>
- <p>{{ $t("settings.share_apk_desc") }}</p>
+ <h2>{{ $t("settings.android_privacy_heading") }}</h2>
+ <p>{{ $t("settings.android_privacy_desc") }}</p>
</div>
</header>
<div class="settings-section__body space-y-4">
+ <label class="setting-toggle">
+ <input
+ v-model="androidShellPrivacy.blockScreenshots"
+ type="checkbox"
+ class="rounded-sm"
+ @change="saveAndroidBlockScreenshots"
+ />
+ <span class="setting-toggle__label">
+ <span class="setting-toggle__title">{{
+ $t("settings.android_block_screenshots")
+ }}</span>
+ <span class="setting-toggle__description">{{
+ $t("settings.android_block_screenshots_desc")
+ }}</span>
+ </span>
+ </label>
+ <label class="setting-toggle">
+ <input
+ v-model="androidShellPrivacy.clearClipboardOnBackground"
+ type="checkbox"
+ class="rounded-sm"
+ @change="saveAndroidClearClipboardOnBackground"
+ />
+ <span class="setting-toggle__label">
+ <span class="setting-toggle__title">{{
+ $t("settings.android_clear_clipboard_on_background")
+ }}</span>
+ <span class="setting-toggle__description">{{
+ $t("settings.android_clear_clipboard_on_background_desc")
+ }}</span>
+ </span>
+ </label>
<button
type="button"
class="btn-maintenance border-blue-200 dark:border-blue-900/30 text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/10 hover:bg-blue-100 dark:hover:bg-blue-900/20"
@@ -3827,6 +3859,10 @@ export default {
closeBehavior: "ask",
trayEnabled: true,
},
+ androidShellPrivacy: {
+ blockScreenshots: false,
+ clearClipboardOnBackground: false,
+ },
reticulumInstance: {
share_instance: true,
local_hops_delta: false,
@@ -4068,6 +4104,7 @@ export default {
this.loadBatteryInterfaceRows();
this.loadDesktopCloseSettings();
this.loadReticulumInstanceSettings();
+ this.loadAndroidShellPrivacy();
},
methods: {
loadBatterySaverPrefsFromStorage() {
@@ -4450,6 +4487,44 @@ export default {
ToastUtils.error(this.$t("settings.share_apk_failed"));
}
},
+ loadAndroidShellPrivacy() {
+ if (!this.isMeshChatXAndroid) {
+ return;
+ }
+ const bridge = new AndroidBridge();
+ this.androidShellPrivacy = {
+ blockScreenshots: bridge.getBlockScreenshots(),
+ clearClipboardOnBackground: bridge.getClearClipboardOnBackground(),
+ };
+ },
+ saveAndroidBlockScreenshots() {
+ const bridge = new AndroidBridge();
+ const enabled = Boolean(this.androidShellPrivacy.blockScreenshots);
+ if (!bridge.setBlockScreenshots(enabled)) {
+ this.androidShellPrivacy.blockScreenshots = !enabled;
+ ToastUtils.error(this.$t("settings.android_privacy_save_failed"));
+ return;
+ }
+ ToastUtils.success(
+ enabled
+ ? this.$t("settings.android_block_screenshots_on")
+ : this.$t("settings.android_block_screenshots_off")
+ );
+ },
+ saveAndroidClearClipboardOnBackground() {
+ const bridge = new AndroidBridge();
+ const enabled = Boolean(this.androidShellPrivacy.clearClipboardOnBackground);
+ if (!bridge.setClearClipboardOnBackground(enabled)) {
+ this.androidShellPrivacy.clearClipboardOnBackground = !enabled;
+ ToastUtils.error(this.$t("settings.android_privacy_save_failed"));
+ return;
+ }
+ ToastUtils.success(
+ enabled
+ ? this.$t("settings.android_clear_clipboard_on_background_on")
+ : this.$t("settings.android_clear_clipboard_on_background_off")
+ );
+ },
matchesSearch(...texts) {
return matchesSettingSearch(texts, (k) => this.$t(k), this.searchQuery);
},
diff --git a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
index adc8df36..59b6f8e9 100644
--- a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
+++ b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
@@ -140,6 +140,15 @@ export const CORE_SETTINGS_SECTION_KEYWORDS = {
"APK",
"Bluetooth",
"Nearby Share",
+ "screenshot",
+ "clipboard",
+ "privacy",
+ "settings.android_privacy_heading",
+ "settings.android_privacy_desc",
+ "settings.android_block_screenshots",
+ "settings.android_block_screenshots_desc",
+ "settings.android_clear_clipboard_on_background",
+ "settings.android_clear_clipboard_on_background_desc",
"settings.share_apk_heading",
"settings.share_apk_desc",
"settings.share_apk",
diff --git a/meshchatx/src/frontend/js/rnode/AndroidBridge.js b/meshchatx/src/frontend/js/rnode/AndroidBridge.js
index 99f4be92..c8c46bc7 100644
--- a/meshchatx/src/frontend/js/rnode/AndroidBridge.js
+++ b/meshchatx/src/frontend/js/rnode/AndroidBridge.js
@@ -143,6 +143,48 @@ export default class AndroidBridge {
return true;
}, false);
}
+
+ /**
+ * Whether FLAG_SECURE is enabled (blocks screenshots and recent-app previews).
+ * Defaults to false when the bridge method is missing.
+ */
+ getBlockScreenshots() {
+ if (!this.bridge || typeof this.bridge.getBlockScreenshots !== "function") {
+ return false;
+ }
+ return safeCall(() => Boolean(this.bridge.getBlockScreenshots()), false);
+ }
+
+ setBlockScreenshots(enabled) {
+ if (!this.bridge || typeof this.bridge.setBlockScreenshots !== "function") {
+ return false;
+ }
+ return safeCall(() => {
+ this.bridge.setBlockScreenshots(Boolean(enabled));
+ return true;
+ }, false);
+ }
+
+ /**
+ * Whether the primary clipboard is cleared when the app goes to the background.
+ * Defaults to false when the bridge method is missing.
+ */
+ getClearClipboardOnBackground() {
+ if (!this.bridge || typeof this.bridge.getClearClipboardOnBackground !== "function") {
+ return false;
+ }
+ return safeCall(() => Boolean(this.bridge.getClearClipboardOnBackground()), false);
+ }
+
+ setClearClipboardOnBackground(enabled) {
+ if (!this.bridge || typeof this.bridge.setClearClipboardOnBackground !== "function") {
+ return false;
+ }
+ return safeCall(() => {
+ this.bridge.setClearClipboardOnBackground(Boolean(enabled));
+ return true;
+ }, false);
+ }
}
AndroidBridge.PERM_BLUETOOTH = PERM_BLUETOOTH;
diff --git a/meshchatx/src/frontend/locales/de.json b/meshchatx/src/frontend/locales/de.json
index c6e8fb22..e7f24a8d 100644
--- a/meshchatx/src/frontend/locales/de.json
+++ b/meshchatx/src/frontend/locales/de.json
@@ -3204,6 +3204,17 @@
"share_apk_short_hint": "Öffnet das Teilen-Menü für diese APK.",
"share_apk": "App teilen (APK)",
"share_apk_failed": "APK konnte nicht geteilt werden.",
+ "android_privacy_heading": "Geräteschutz",
+ "android_privacy_desc": "Optionale Android-Schutzfunktionen. Alle Optionen sind standardmäßig aus, damit Screenshots und Zwischenablage normal funktionieren.",
+ "android_block_screenshots": "Screenshots und App-Wechsler-Vorschau blockieren",
+ "android_block_screenshots_desc": "Setzt das Android-Flag für sichere Fenster, damit Screenshots und die App-Wechsler-Vorschau MeshChatX nicht erfassen können.",
+ "android_block_screenshots_on": "Screenshot-Sperre aktiviert",
+ "android_block_screenshots_off": "Screenshot-Sperre deaktiviert",
+ "android_clear_clipboard_on_background": "Zwischenablage beim Verlassen der App leeren",
+ "android_clear_clipboard_on_background_desc": "Leert die System-Zwischenablage, wenn MeshChatX in den Hintergrund geht, damit kopierter Nachrichtentext schwerer an andere Apps gelangt.",
+ "android_clear_clipboard_on_background_on": "Zwischenablage wird beim Wechsel in den Hintergrund geleert",
+ "android_clear_clipboard_on_background_off": "Zwischenablage wird beim Hintergrundwechsel nicht mehr geleert",
+ "android_privacy_save_failed": "Android-Datenschutzeinstellung konnte nicht aktualisiert werden.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json
index 15232c16..d771c557 100644
--- a/meshchatx/src/frontend/locales/en.json
+++ b/meshchatx/src/frontend/locales/en.json
@@ -1804,6 +1804,17 @@
"share_apk_short_hint": "Opens the system share sheet for this APK.",
"share_apk": "Share app (APK)",
"share_apk_failed": "Could not open the share sheet for the APK.",
+ "android_privacy_heading": "Device privacy",
+ "android_privacy_desc": "Optional Android shell protections. All options are off by default so normal screenshots and clipboard use keep working.",
+ "android_block_screenshots": "Block screenshots and recent-app previews",
+ "android_block_screenshots_desc": "Uses the Android secure-window flag so screenshots and the app switcher preview cannot capture MeshChatX content.",
+ "android_block_screenshots_on": "Screenshot blocking enabled",
+ "android_block_screenshots_off": "Screenshot blocking disabled",
+ "android_clear_clipboard_on_background": "Clear clipboard when leaving the app",
+ "android_clear_clipboard_on_background_desc": "Clears the system clipboard when MeshChatX goes to the background so copied message text is harder to leak to other apps.",
+ "android_clear_clipboard_on_background_on": "Clipboard will clear when the app backgrounds",
+ "android_clear_clipboard_on_background_off": "Clipboard will no longer clear on background",
+ "android_privacy_save_failed": "Could not update Android privacy setting.",
"battery": {
"eyebrow": "Power",
"title": "Battery saver",
diff --git a/meshchatx/src/frontend/locales/es.json b/meshchatx/src/frontend/locales/es.json
index 5bad2b5c..26e1b7aa 100644
--- a/meshchatx/src/frontend/locales/es.json
+++ b/meshchatx/src/frontend/locales/es.json
@@ -1785,6 +1785,17 @@
"share_apk_short_hint": "Abre el menú del sistema para compartir este APK.",
"share_apk": "Compartir app (APK)",
"share_apk_failed": "No se pudo compartir el APK.",
+ "android_privacy_heading": "Privacidad del dispositivo",
+ "android_privacy_desc": "Protecciones opcionales de Android. Todas las opciones están desactivadas por defecto para que capturas y portapapeles sigan funcionando.",
+ "android_block_screenshots": "Bloquear capturas y vista previa de apps recientes",
+ "android_block_screenshots_desc": "Usa el indicador de ventana segura de Android para que capturas y el selector de apps no muestren contenido de MeshChatX.",
+ "android_block_screenshots_on": "Bloqueo de capturas activado",
+ "android_block_screenshots_off": "Bloqueo de capturas desactivado",
+ "android_clear_clipboard_on_background": "Vaciar el portapapeles al salir de la app",
+ "android_clear_clipboard_on_background_desc": "Vacía el portapapeles del sistema cuando MeshChatX pasa a segundo plano para dificultar fugas de texto copiado a otras apps.",
+ "android_clear_clipboard_on_background_on": "El portapapeles se vaciará al pasar a segundo plano",
+ "android_clear_clipboard_on_background_off": "El portapapeles ya no se vaciará en segundo plano",
+ "android_privacy_save_failed": "No se pudo actualizar el ajuste de privacidad de Android.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/fi.json b/meshchatx/src/frontend/locales/fi.json
index 362dd022..89071df0 100644
--- a/meshchatx/src/frontend/locales/fi.json
+++ b/meshchatx/src/frontend/locales/fi.json
@@ -1801,6 +1801,17 @@
"share_apk_short_hint": "Avaa järjestelmän jakoikkunan sovelluksen APK:lle.",
"share_apk": "Jaa sovellus (APK)",
"share_apk_failed": "Jakamisikkunan avaus epäonnistui.",
+ "android_privacy_heading": "Laitteen yksityisyys",
+ "android_privacy_desc": "Valinnaiset Android-suojaukset. Kaikki asetukset ovat oletuksena pois, jotta kuvakaappaukset ja leikepöytä toimivat normaalisti.",
+ "android_block_screenshots": "Estä kuvakaappaukset ja viimeisimpien sovellusten esikatselut",
+ "android_block_screenshots_desc": "Käyttää Androidin turvallisen ikkunan lippua, jotta kuvakaappaukset ja sovellusvaihtaja eivät näytä MeshChatX-sisältöä.",
+ "android_block_screenshots_on": "Kuvakaappausten esto käytössä",
+ "android_block_screenshots_off": "Kuvakaappausten esto pois käytöstä",
+ "android_clear_clipboard_on_background": "Tyhjennä leikepöytä sovelluksesta poistuttaessa",
+ "android_clear_clipboard_on_background_desc": "Tyhjentää järjestelmän leikepöydän kun MeshChatX siirtyy taustalle, jotta kopioitu viestiteksti ei vuoda helposti muihin sovelluksiin.",
+ "android_clear_clipboard_on_background_on": "Leikepöytä tyhjennetään taustalle siirryttäessä",
+ "android_clear_clipboard_on_background_off": "Leikepöytää ei enää tyhjennetä taustalle siirryttäessä",
+ "android_privacy_save_failed": "Android-yksityisyysasetusta ei voitu päivittää.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Käytä nopeampaa moottoria (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/fr.json b/meshchatx/src/frontend/locales/fr.json
index ca2fb3d4..a0385f7d 100644
--- a/meshchatx/src/frontend/locales/fr.json
+++ b/meshchatx/src/frontend/locales/fr.json
@@ -1785,6 +1785,17 @@
"share_apk_short_hint": "Ouvre la feuille de partage système pour cet APK.",
"share_apk": "Partager l'app (APK)",
"share_apk_failed": "Impossible de partager l'APK.",
+ "android_privacy_heading": "Confidentialité de l'appareil",
+ "android_privacy_desc": "Protections Android optionnelles. Toutes les options sont désactivées par défaut pour que captures d'écran et presse-papiers restent utilisables.",
+ "android_block_screenshots": "Bloquer captures d'écran et aperçus d'applications récentes",
+ "android_block_screenshots_desc": "Utilise l'indicateur de fenêtre sécurisée Android pour empêcher captures et aperçus du sélecteur d'apps de montrer MeshChatX.",
+ "android_block_screenshots_on": "Blocage des captures activé",
+ "android_block_screenshots_off": "Blocage des captures désactivé",
+ "android_clear_clipboard_on_background": "Vider le presse-papiers en quittant l'application",
+ "android_clear_clipboard_on_background_desc": "Vide le presse-papiers système lorsque MeshChatX passe en arrière-plan pour limiter les fuites de texte copié vers d'autres apps.",
+ "android_clear_clipboard_on_background_on": "Le presse-papiers sera vidé en arrière-plan",
+ "android_clear_clipboard_on_background_off": "Le presse-papiers ne sera plus vidé en arrière-plan",
+ "android_privacy_save_failed": "Impossible de mettre à jour le réglage de confidentialité Android.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/it.json b/meshchatx/src/frontend/locales/it.json
index d96aac05..97c16933 100644
--- a/meshchatx/src/frontend/locales/it.json
+++ b/meshchatx/src/frontend/locales/it.json
@@ -1837,6 +1837,17 @@
"share_apk_short_hint": "Apre la condivisione di sistema per questo APK.",
"share_apk": "Condividi app (APK)",
"share_apk_failed": "Impossibile condividere l'APK.",
+ "android_privacy_heading": "Privacy del dispositivo",
+ "android_privacy_desc": "Protezioni Android opzionali. Tutte le opzioni sono disattivate di default così screenshot e appunti restano utilizzabili.",
+ "android_block_screenshots": "Blocca screenshot e anteprime app recenti",
+ "android_block_screenshots_desc": "Usa il flag di finestra sicura di Android così screenshot e anteprima dello switcher non catturano MeshChatX.",
+ "android_block_screenshots_on": "Blocco screenshot attivato",
+ "android_block_screenshots_off": "Blocco screenshot disattivato",
+ "android_clear_clipboard_on_background": "Svuota gli appunti uscendo dall'app",
+ "android_clear_clipboard_on_background_desc": "Svuota gli appunti di sistema quando MeshChatX va in background per rendere più difficile la fuga di testo copiato verso altre app.",
+ "android_clear_clipboard_on_background_on": "Gli appunti verranno svuotati in background",
+ "android_clear_clipboard_on_background_off": "Gli appunti non verranno più svuotati in background",
+ "android_privacy_save_failed": "Impossibile aggiornare l'impostazione privacy Android.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/nl.json b/meshchatx/src/frontend/locales/nl.json
index 44b3a6e3..4ee2bdf7 100644
--- a/meshchatx/src/frontend/locales/nl.json
+++ b/meshchatx/src/frontend/locales/nl.json
@@ -1785,6 +1785,17 @@
"share_apk_short_hint": "Opent het systeemdeelvenster voor deze APK.",
"share_apk": "App delen (APK)",
"share_apk_failed": "APK kan niet worden gedeeld.",
+ "android_privacy_heading": "Apparaatprivacy",
+ "android_privacy_desc": "Optionele Android-bescherming. Alles staat standaard uit zodat schermopnamen en klembord normaal blijven werken.",
+ "android_block_screenshots": "Schermopnamen en recente-app-voorbeelden blokkeren",
+ "android_block_screenshots_desc": "Gebruikt de Android beveiligde-venster vlag zodat schermopnamen en de app-wisselaar MeshChatX niet kunnen vastleggen.",
+ "android_block_screenshots_on": "Schermopnameblokkering ingeschakeld",
+ "android_block_screenshots_off": "Schermopnameblokkering uitgeschakeld",
+ "android_clear_clipboard_on_background": "Klembord wissen bij verlaten van de app",
+ "android_clear_clipboard_on_background_desc": "Wist het systeemklembord wanneer MeshChatX naar de achtergrond gaat zodat gekopieerde tekst moeilijker naar andere apps lekt.",
+ "android_clear_clipboard_on_background_on": "Klembord wordt gewist bij achtergrond",
+ "android_clear_clipboard_on_background_off": "Klembord wordt niet meer gewist bij achtergrond",
+ "android_privacy_save_failed": "Android-privacy-instelling kon niet worden bijgewerkt.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/ru.json b/meshchatx/src/frontend/locales/ru.json
index 97d68433..bf197fa5 100644
--- a/meshchatx/src/frontend/locales/ru.json
+++ b/meshchatx/src/frontend/locales/ru.json
@@ -3204,6 +3204,17 @@
"share_apk_short_hint": "Открывает системное меню для отправки этого APK.",
"share_apk": "Поделиться приложением (APK)",
"share_apk_failed": "Не удалось поделиться APK.",
+ "android_privacy_heading": "Конфиденциальность устройства",
+ "android_privacy_desc": "Дополнительные защиты Android. Все параметры по умолчанию выключены, чтобы снимки экрана и буфер обмена работали как обычно.",
+ "android_block_screenshots": "Блокировать снимки экрана и превью недавних приложений",
+ "android_block_screenshots_desc": "Использует флаг защищённого окна Android, чтобы снимки экрана и превью переключателя приложений не показывали MeshChatX.",
+ "android_block_screenshots_on": "Блокировка снимков экрана включена",
+ "android_block_screenshots_off": "Блокировка снимков экрана выключена",
+ "android_clear_clipboard_on_background": "Очищать буфер обмена при выходе из приложения",
+ "android_clear_clipboard_on_background_desc": "Очищает системный буфер обмена, когда MeshChatX уходит в фон, чтобы скопированный текст сообщений было сложнее перехватить другими приложениями.",
+ "android_clear_clipboard_on_background_on": "Буфер обмена будет очищаться при уходе в фон",
+ "android_clear_clipboard_on_background_off": "Буфер обмена больше не будет очищаться при уходе в фон",
+ "android_privacy_save_failed": "Не удалось обновить настройку конфиденциальности Android.",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/meshchatx/src/frontend/locales/zh.json b/meshchatx/src/frontend/locales/zh.json
index 3a13cac2..0f6d22fa 100644
--- a/meshchatx/src/frontend/locales/zh.json
+++ b/meshchatx/src/frontend/locales/zh.json
@@ -1785,6 +1785,17 @@
"share_apk_short_hint": "打开系统分享面板以发送此 APK。",
"share_apk": "分享应用(APK)",
"share_apk_failed": "无法打开 APK 分享。",
+ "android_privacy_heading": "设备隐私",
+ "android_privacy_desc": "可选的 Android 外壳保护。默认全部关闭,以便截屏和剪贴板照常使用。",
+ "android_block_screenshots": "禁止截屏与最近任务预览",
+ "android_block_screenshots_desc": "启用 Android 安全窗口标志,使截屏和多任务预览无法捕获 MeshChatX 内容。",
+ "android_block_screenshots_on": "已启用截屏阻止",
+ "android_block_screenshots_off": "已关闭截屏阻止",
+ "android_clear_clipboard_on_background": "离开应用时清空剪贴板",
+ "android_clear_clipboard_on_background_desc": "当 MeshChatX 进入后台时清空系统剪贴板,降低已复制消息文本泄露到其他应用的风险。",
+ "android_clear_clipboard_on_background_on": "应用进入后台时将清空剪贴板",
+ "android_clear_clipboard_on_background_off": "应用进入后台时不再清空剪贴板",
+ "android_privacy_save_failed": "无法更新 Android 隐私设置。",
"nomad_micron_wasm_title": "Micron (WASM)",
"nomad_micron_wasm_desc_before_link": "Optional WASM renderer (",
"nomad_micron_wasm_link_label": "micron-parser-go",
diff --git a/tests/frontend/RNodeAndroidBridge.test.js b/tests/frontend/RNodeAndroidBridge.test.js
index c83d1c51..81822922 100644
--- a/tests/frontend/RNodeAndroidBridge.test.js
+++ b/tests/frontend/RNodeAndroidBridge.test.js
@@ -90,4 +90,28 @@ describe("AndroidBridge", () => {
const ab = new AndroidBridge(null, env);
expect(ab.isAvailable()).toBe(true);
});
+
+ it("privacy helpers default to false when methods are missing", () => {
+ const ab = new AndroidBridge({}, {});
+ expect(ab.getBlockScreenshots()).toBe(false);
+ expect(ab.getClearClipboardOnBackground()).toBe(false);
+ expect(ab.setBlockScreenshots(true)).toBe(false);
+ expect(ab.setClearClipboardOnBackground(true)).toBe(false);
+ });
+
+ it("privacy helpers delegate to the bridge", () => {
+ const bridge = {
+ getBlockScreenshots: vi.fn().mockReturnValue(true),
+ setBlockScreenshots: vi.fn(),
+ getClearClipboardOnBackground: vi.fn().mockReturnValue(false),
+ setClearClipboardOnBackground: vi.fn(),
+ };
+ const ab = new AndroidBridge(bridge, {});
+ expect(ab.getBlockScreenshots()).toBe(true);
+ expect(ab.getClearClipboardOnBackground()).toBe(false);
+ expect(ab.setBlockScreenshots(true)).toBe(true);
+ expect(ab.setClearClipboardOnBackground(true)).toBe(true);
+ expect(bridge.setBlockScreenshots).toHaveBeenCalledWith(true);
+ expect(bridge.setClearClipboardOnBackground).toHaveBeenCalledWith(true);
+ });
});
diff --git a/tests/frontend/behaviorContracts.test.js b/tests/frontend/behaviorContracts.test.js
index bf087e94..bc50dcd8 100644
--- a/tests/frontend/behaviorContracts.test.js
+++ b/tests/frontend/behaviorContracts.test.js
@@ -81,6 +81,23 @@ describe("behavior contracts: user-visible wiring must stay connected", () => {
expect(src).toContain("persistMeshchatDownload");
expect(src).toContain("MeshChatXAndroidBridge");
});
+
+ it("Android MainActivity supports opt-in screenshot and clipboard privacy", () => {
+ const src = readSource("android/app/src/main/java/com/meshchatx/MainActivity.java");
+ expect(src).toContain("FLAG_SECURE");
+ expect(src).toContain("PREF_BLOCK_SCREENSHOTS");
+ expect(src).toContain("setBlockScreenshots");
+ expect(src).toContain("PREF_CLEAR_CLIPBOARD_ON_BACKGROUND");
+ expect(src).toContain("setClearClipboardOnBackground");
+ expect(src).toContain("setFilterTouchesWhenObscured");
+ expect(src).toMatch(/getBoolean\(\s*PREF_BLOCK_SCREENSHOTS\s*,\s*false\s*\)/);
+ expect(src).toMatch(/getBoolean\(\s*PREF_CLEAR_CLIPBOARD_ON_BACKGROUND\s*,\s*false\s*\)/);
+ });
+
+ it("Android backup of app data is disabled", () => {
+ const src = readSource("android/app/src/main/AndroidManifest.xml");
+ expect(src).toMatch(/android:allowBackup\s*=\s*"false"/);
+ });
});
describe("nomad mesh file upload", () => {
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────